package org.fjsei.yewu.jpa;


import lombok.SneakyThrows;
import md.specialEqp.Eqp;
import org.apache.commons.collections4.ListUtils;
import org.fjsei.yewu.exception.RecordUpsertException;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.util.Assert;

import jakarta.persistence.*;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static org.springframework.test.util.ReflectionTestUtils.getField;


//import static sun.reflect.misc.FieldUtil.getField;

/**
 * 起因是针对CRDB数据库的upsert and upsertAll优化配置底下repositoryBaseClass = ExtendedJpaRepositoryImpl.class是CRDB,不是这种数据库用不了。
  去掉repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class,报错PropertyReferenceException: No property 'findAllNc' found for type 'Unit'!淘汰用法改Slice;
 传递链：CustomRepositoryFactoryBean.createRepositoryFactory:>CustomRepositoryFactory.RepositoryBaseClass===CustomRepositoryImpl:>QuerydslNcExecutorImpl;起因是count()耗时findAllNc。
 #导致了:旧的JPA改造基础类CustomRepositoryImpl实际上和ExtendedJpaRepositoryImpl是替换关系的冲突，所以报错？反射Fragment method=null。
 *提升性能有代价=兼容性问题：依赖于注解@Table @Column。nativeQuery代表在执行这个方法的时候执行原生sql语句，直接写数据库中的实际表名和表中的实际字段名;依赖物理数据库。
 * ExtendedJpaRepository能不用就不用，除非真的需要提升性能。
 * 不能用abstract class报错Failed to instantiate [org.fjsei.yewu.jpa.ExtendedJpaRepositoryImpl]: Is it an abstract class?;
 * 【毛病】QuartzJob情形用.upsertAll(); 报错[ERROR: cannot execute UPSERT in a read-only transaction]不是正常的spring Bean注入。
* */
public class ExtendedJpaRepositoryImpl<T,ID> extends
        SimpleJpaRepository<T,ID> implements ExtendedJpaRepository<T,ID>{

    private JpaEntityInformation<T, ?> entityInformation;
    private EntityManager entityManager;
    private static final String COMMA = ",";
    private static final String OPEN_BRACKET = "(";
    private static final String CLOSE_BRACKET = ")";
    private static final int MAX_BIND_VARIABLE_LIMIT = 32767-1;

    public ExtendedJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityInformation = entityInformation;
        this.entityManager = entityManager;
    }

    private List<Field> getColumnFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while(clazz != Object.class){
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields.stream().filter(field -> field.isAnnotationPresent(Column.class) || field.isAnnotationPresent(JoinColumn.class)
                   || field.isAnnotationPresent(EmbeddedId.class)).collect(Collectors.toList());
    }
/**没用注解的， 其他更多字段的
 * 关联对象？ join()??
 * */
    private String populateTableColumns(List<Field> columnFields){
        StringBuilder queryBuilder = new StringBuilder();
        for(Field field : columnFields){
            String columnName;
            if(field.isAnnotationPresent(Column.class))
                columnName = field.getAnnotation(Column.class).name();
            else if(field.isAnnotationPresent(JoinColumn.class))
                columnName = field.getAnnotation(JoinColumn.class).name();
            else {
                queryBuilder.append(populateTableColumns(getColumnFields(field.getType()))).append(COMMA);
                continue;
            }
            if(!StringUtils.hasText(columnName))    columnName=field.getName();
            queryBuilder.append(columnName).append(COMMA);
        }
        queryBuilder.deleteCharAt(queryBuilder.lastIndexOf(COMMA));
        return queryBuilder.toString();
    }

    private String formInitialQuery(List<Field> columnFields, String tableName){
        return "UPSERT INTO " +
                tableName +
                OPEN_BRACKET +
                populateTableColumns(columnFields) +
                CLOSE_BRACKET +
                " VALUES ";
    }

    private String populateColumnParams(List<Field> columnFields, int index){
        StringBuilder valuesBuilder = new StringBuilder();
        for(Field field : columnFields){
            if(field.isAnnotationPresent(EmbeddedId.class)){
                valuesBuilder.append(populateColumnParams(getColumnFields(field.getType()),index)).append(COMMA);
            }else{
                valuesBuilder.append(":");
                valuesBuilder.append(field.getName().trim()).append(index);
                valuesBuilder.append(COMMA);
            }
        }
        valuesBuilder.deleteCharAt(valuesBuilder.lastIndexOf(COMMA));
        return valuesBuilder.toString();
    }

    @SneakyThrows
    private <K> void populateColumnValues(List<Field> columnFields, K entity, Query nativeQuery, int index){
        for(Field field : columnFields){
            try {
                field.setAccessible(true);
                if(field.isAnnotationPresent(EmbeddedId.class)){
                    final ID primaryKey = (ID) entityInformation.getId((T) entity);
                    populateColumnValues(getColumnFields(field.getType()), primaryKey, nativeQuery, index);
                }else {
                    nativeQuery.setParameter(field.getName() + index, field.get(entity));
                }
                field.setAccessible(false);
            }catch (IllegalAccessException e){
                throw new RecordUpsertException("Exception while upserting a record",e);
            }
        }
    }
    /**缺点：必须是预定义@Column(JoinColumn(EmbeddedId注解字段才能保存的，其它字段不管了。
     * */
    @Deprecated
    @Override
    public T upsert(T entity) {
        Assert.notNull(entity, "Entity must not be null.");
        final List<Field> columnFields = getColumnFields(entity.getClass());
        final String query = formInitialQuery(columnFields, entity.getClass().getAnnotation(Table.class).name()) + OPEN_BRACKET +
                populateColumnParams(columnFields,0) + CLOSE_BRACKET;
        final Query nativeQuery = entityManager.createNativeQuery(query,entity.getClass());
        populateColumnValues(columnFields,entity,nativeQuery,0);
        nativeQuery.executeUpdate();
        return entity;
    }

    /**注意！这个办法，性能有些情况下可能不如直接用saveAll做的。
     * 一个命令包处理的数据量也要适当，不能太多，@多了反而更慢@，看情况调整entities的实际数量。
     * 另外，若包含字段数较多的实体类，可能对比saveAll感觉反而更慢。
     * 【缺点】对于QuartzJob情形若用.upsertAll() 报错！
     * */
    @Override
    @SneakyThrows
    public List<T> upsertAll(List<T> entities) {
        Assert.notNull(entities, "Entity must not be null.");
        Assert.notEmpty(entities,"Entity must not be empty.");
        final T sampleEntity = entities.get(0);
        final List<Field> columnFields = getColumnFields(sampleEntity.getClass());
        Assert.notEmpty(columnFields,"Define the entity with proper JPA annotations");

        final int bindVariableCount = columnFields.size()*entities.size();
        final int entitiesPerStatement = bindVariableCount > MAX_BIND_VARIABLE_LIMIT ? MAX_BIND_VARIABLE_LIMIT/columnFields.size() : entities.size();
        //partition()需要单独引入一个工具包commons-collections4：
        List<List<T>> partitionedEntityList = ListUtils.partition(entities, entitiesPerStatement);
        partitionedEntityList.forEach(partitionedEntities -> {
            StringBuilder queryBuilder = new StringBuilder();
            queryBuilder.append(formInitialQuery(columnFields, sampleEntity.getClass().getAnnotation(Table.class).name()));
            AtomicInteger index = new AtomicInteger(0);
            partitionedEntities.forEach(entity -> queryBuilder.append(OPEN_BRACKET).append(populateColumnParams(columnFields, index.getAndIncrement()))
                    .append(CLOSE_BRACKET).append(COMMA));
            queryBuilder.deleteCharAt(queryBuilder.lastIndexOf(COMMA));
            final Query nativeQuery = entityManager.createNativeQuery(queryBuilder.toString(), sampleEntity.getClass());
            index.set(0);
            partitionedEntities.forEach(entity -> populateColumnValues(columnFields, entity, nativeQuery, index.getAndIncrement()));
            nativeQuery.executeUpdate();
        });
        return entities;
    }

/*    @Override
    public <T1> Slice<T1> readAllBy(Pageable pageable, Class<T1> type) {
        return null;
    }*/


    /*@Override  //quertDsl
    public Page<T> findAll(Predicate predicate, Pageable pageable) {
        int oneMore = pageable.getPageSize() + 1;
        JPQLQuery query = createQuery(predicate)
                .offset(pageable.getOffset())
                .limit(oneMore);

        Sort sort = pageable.getSort();
        query = querydsl.applySorting(sort, query);

        List<T> entities = query.list(path);

        int size = entities.size();
        if (size > pageable.getPageSize())
            entities.remove(size - 1);

        return new PageImpl<>(entities, pageable, pageable.getOffset() + size);
    }*/


    @Deprecated
    @Override
    @SneakyThrows
    public List<T> upsertAll(List<T> entities,List<String> fileds) {
        Assert.notNull(entities, "Entity must not be null.");
        Assert.notEmpty(entities,"Entity must not be empty.");
        final T sampleEntity = entities.get(0);
        Class<?> clazz=sampleEntity.getClass();
        Class<?> finalClazz = clazz;
        final List<Field> columnFields = fileds.stream().map(field -> {
            try {
                return finalClazz.getDeclaredField(field);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
        Assert.notEmpty(columnFields,"Define the entity with proper JPA annotations");
        final int bindVariableCount = columnFields.size()*entities.size();
        final int entitiesPerStatement = bindVariableCount > MAX_BIND_VARIABLE_LIMIT ? MAX_BIND_VARIABLE_LIMIT/columnFields.size() : entities.size();
        //partition()需要单独引入一个工具包commons-collections4：
        List<List<T>> partitionedEntityList = ListUtils.partition(entities, entitiesPerStatement);
        partitionedEntityList.forEach(partitionedEntities -> {
            StringBuilder queryBuilder = new StringBuilder();
            queryBuilder.append(formInitialQuery(columnFields, sampleEntity.getClass().getAnnotation(Table.class).name()));
            AtomicInteger index = new AtomicInteger(0);
            partitionedEntities.forEach(entity -> queryBuilder.append(OPEN_BRACKET).append(populateColumnParams(columnFields, index.getAndIncrement()))
                    .append(CLOSE_BRACKET).append(COMMA));
            queryBuilder.deleteCharAt(queryBuilder.lastIndexOf(COMMA));
            final Query nativeQuery = entityManager.createNativeQuery(queryBuilder.toString(), sampleEntity.getClass());
            index.set(0);
            partitionedEntities.forEach(entity -> populateColumnValues(columnFields, entity, nativeQuery, index.getAndIncrement()));
            nativeQuery.executeUpdate();
        });
        return entities;
    }
}
